﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.IO;
using System.Diagnostics;
using System.Threading;

using Microsoft.Win32; // registry functions

namespace SteamMoverWPF
{
    /// <summary>
    /// 
    /// TODO:
    /// - Ability to find Steam\steamapps\common folder (main location, junctions made here) (done)
    /// - Ability to store multiple alternate folder locations (z:\games, x:\games) (done)
    /// - Ability to store multiple main folder locations (done)
    /// - Ability to move between alternate folder locations (delete junction, move files from z:\games\abc to x:\games\abc, create new junction) (done)
    /// 
    /// - Add ability to perform moves & get updates asynchonously (done)
    /// - Add custom names for locations (Main, fast, faster, RAMDISK)
    /// - Add better Icon to application
    /// - Add ability to customize config from application
    /// 
    /// </summary>
    public partial class MainWindow : Window
    {
        public SteamMoverConfig config;
        private List<Game> games;
        private FileMoverQueue moveQueue;
        private string steampath;

        public MainWindow()
        {
            InitializeComponent();

            config = new SteamMoverConfig("config.json");
            config.LoadJSON();

            moveQueue = new FileMoverQueue();

            steampath = GetSteamInstallPath();
            if (!IsSteamInstallPathValid(steampath))
                steampath = PromptUserForSteamPath();

            string[] mainpaths = GetMainPathsFromSteam(steampath);

            BuildGamesList(mainpaths);

            RefreshGrid();

            PopulateSizes(games); // this is intended
        }

        private string[] GetMainPathsFromSteam(string steampath)
        {
            string configfile = steampath + @"\config\config.vdf";
            if (!File.Exists(configfile))
                return new string[0];

            List<string> mainpaths = new List<string>();

            string defaultgamepath = steampath + @"\steamapps\common\";
            if (Directory.Exists(defaultgamepath))
                mainpaths.Add(defaultgamepath);

            // i'm trying to save on parsing the whole thing, not sure if it does though.
            FileStream fs = null;
            StreamReader sr = null;

            try
            {
                fs = new FileStream(configfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                sr = new StreamReader(fs);
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.Contains("BaseI")) // "BaseInstallFolder_*"
                    {
                        string[] spl = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                        if (spl == null || spl.Length != 2)
                            continue;
                        string path = spl[1].Replace("\"", "").Replace(@"\\", @"\");
                        if (Directory.Exists(path))
                            mainpaths.Add(path + @"\steamapps\common\");
                    }
                }
            }
            finally
            {
                if (sr != null)
                    sr.Dispose();
                if (fs != null)
                    fs.Dispose();
            }


            return mainpaths.ToArray();
        }

        private string PromptUserForSteamPath()
        {
            using (System.Windows.Forms.FolderBrowserDialog fbdialog =
                new System.Windows.Forms.FolderBrowserDialog())
            {
                fbdialog.ShowNewFolderButton = false;

                while (fbdialog.SelectedPath == null
                        || !IsSteamInstallPathValid(fbdialog.SelectedPath))
                {
                    MessageBoxResult mboxresult =
                        MessageBox.Show(
                            string.Format(
                                "{0} could not find the location your Steam installation. "
                                + "Please select the folder containing \"{1}\", or cancel to exit.",
                                this.Title, config.SteamExeName),
                            "Could not locate Steam.",
                            MessageBoxButton.OKCancel,
                            MessageBoxImage.Warning,
                            MessageBoxResult.OK);

                    if (mboxresult != MessageBoxResult.OK)
                        Environment.Exit(1);

                    System.Windows.Forms.DialogResult result = fbdialog.ShowDialog();
                    if (result != System.Windows.Forms.DialogResult.OK)
                        Environment.Exit(1);
                }

                return fbdialog.SelectedPath;
            }
        }

        private bool IsSteamInstallPathValid(string path)
        {
            if (path == null || path == string.Empty)
                return false;
            return Directory.Exists(path + config.SteamExeName);
        }

        private string GetSteamInstallPath()
        {
            string installpath = 
                (string)Registry.GetValue(
                    @"HKEY_CURRENT_USER\Software\Valve\Steam", 
                    "SteamPath", 
                    string.Empty);

            if (!Directory.Exists(installpath))
                return string.Empty;

            return installpath;
        }

        private void BuildGamesList(string[] mainpaths)
        {
            //games = config.MainLocations.SelectMany(h => ReadMainLocation(h)).ToList();
            games = new List<Game>();
            foreach (string path in mainpaths)
                games.AddRange(ReadMainLocation(path));
        }

        private void AddGameToList(string frompath)
        {
            if (!Directory.Exists(frompath))
                return;

            Game toadd = CreateGame(new DirectoryInfo(frompath));
            foreach (Game g in games)
                if (g.Name == toadd.Name)
                    return;

            games.Add(toadd);
        }
        
        private void SearchForGames()
        {
            throw new NotImplementedException();
            /*
            // should make the drives to search selectable later
            DriveInfo[] fixeddrives = (from drive in DriveInfo.GetDrives()
                                       where drive.DriveType == DriveType.Fixed
                                       select drive).ToArray();

            foreach (DriveInfo drive in fixeddrives)
            {
                //
            }
             */
        }

        private void CrawlForGames(string path, int depth = 0)
        {
            if (depth >= config.CrawlDepth 
                || !Directory.Exists(path))
                return;

            string[] listing = Directory.EnumerateDirectories(path).ToArray();
            if (File.Exists(path + "steam.dll"))
            {
                foreach (string dir in listing)
                {
                    if (dir.ToLower() == "steamapps"
                        && Directory.Exists(path + @"\steamapps\common\"))
                    {
                        AddGameToList(path + @"\steamapps\common\");
                        break;
                    }
                }
            }

            foreach(string dir in listing)
                CrawlForGames(dir, depth + 1);
        }
        

        private void RefreshGrid()
        {
            dataGames.ItemsSource = games;
            dataGames.Items.Refresh();
        }

        public async Task PopulateSizes(List<Game> games)
        {
            //int index = 0;
            foreach (var game in games)
            {
                // don't really need to test if it's a junction or not, let the filesystem handle it
                await UpdateGameSize(game);
                
                //game.Size = await GetDirectorySize(game.MainLocation); 
                //if (index >= 5)
                //{
                //    RefreshGrid();
                //    index = 0;
                //}
                //else
                //    index++;
            }
            RefreshGrid();
        }

        public async Task UpdateGameSize(Game g)
        {
            g.Size = g.IsJunction ? 
                await GetDirectorySize(g.AlternateLocation) 
                : await GetDirectorySize(g.MainLocation); 
        }

        public static async Task<long> GetDirectorySize(DirectoryInfo dir)
        {
            var test = dir.EnumerateFiles("*", SearchOption.AllDirectories);
            return dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(file => file.Length);
            //return await Task.Run<long>(() => { return dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(file => file.Length); });
        }

        public void UpdateGameFolder(Game game)
        {
            Game newGame = CreateGame(new DirectoryInfo(game.MainLocation.FullName));

            //game.IsJunction = newGame.IsJunction;
            //game.AlternateLocation = newGame.AlternateLocation;
        }

        public Game CreateGame(DirectoryInfo g)
        {
            bool isJunction = g.Attributes.HasFlag(FileAttributes.ReparsePoint);
            return new Game { 
                /*IsJunction = isJunction,*/ 
                Name = g.Name, MainLocation = g, 
                /* AlternateLocation = isJunction ? new DirectoryInfo(JunctionPoint.GetTarget(g.FullName))  : null*/ };
        }

        private IEnumerable<Game> ReadMainLocation(String location)
        {
            DirectoryInfo d = new DirectoryInfo(location);
            if (!d.Exists)
                return new List<Game>();

            var gameFolders = d.GetDirectories(); // get all folders in MainLocation

            return gameFolders.Select(g => CreateGame(g));
        }

        private async void MoveGame(object sender, RoutedEventArgs e)
        {
            //Button moveButton = (Button)sender;

            var selectedGame = ((FrameworkElement)sender).DataContext as Game;

            if (!selectedGame.IsQueued)
            {
                DestinationWindow d = new DestinationWindow(selectedGame);
                d.SetAlternateLocations(config.AlternateLocations);

                if (d.ShowDialog() == true)
                {
                    moveQueue.Add(selectedGame, d.selectedLocation + "\\" + selectedGame.Name);
                    await moveQueue.ExecuteQueue();
                    UpdateGameFolder(selectedGame);
                    RefreshGrid();
                }
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            
        }

        private void cmdRefresh_Click(object sender, RoutedEventArgs e)
        {
            BuildGamesList(GetMainPathsFromSteam(steampath));

            RefreshGrid();

            PopulateSizes(games); 
        }
    }
}
